#ifndef LGE_JOBSUBSYSTEM_H
#define LGE_JOBSUBSYSTEM_H

#include <core/lge_types.h>
#include <core/lge_subsystem.h>
#include <core/lge_threadpool.h>

#include <job/lge_jobqueue.h>

#include <vector>

class JobSubsystem : public Subsystem<JobSubsystem>
{
public:
	struct Configuration
	{
		/**
		  * \brief The number of threads to use within the job subsystem.
		  *
		  * If set to 0, the subsystem will use as many threads as cpu cores.
		  */
		SizeT mThreadCount;

		SizeT mMaxJobsPerQueue;

		Configuration(SizeT threadCount=0, SizeT maxJobs=100)
		: mThreadCount(threadCount), mMaxJobsPerQueue(maxJobs)
		{
		}
	};

	static const SizeT MainThread = 0;

private:
	Configuration mConfiguration;

	ThreadPool mThreadPool;

	/**
	  * \brief Hold the list of all job queues
	  */
	std::vector<JobQueue*> mJobQueues;

	volatile bool mExitThreads;

	void run(SizeT threadId)
	{
		JobQueue * jobQueue = mJobQueues[threadId];

		Pair<Job, JobState *> jobPair(Job(), NULL);

		bool result;

		while (!mExitThreads)
		{
			// Pop a job (blocking)
			jobQueue->pop(jobPair);

			// Run the job
			result = jobPair.mFirst();

			if (!jobPair.mSecond)
				continue;

			jobPair.mSecond->mResult = result;
			jobPair.mSecond->mDone = true;
		}
	}

public:
	bool init(const Configuration & config);

	bool release();

	/**
	  * \brief Push a new job into the job system
	  *
	  * A job is pushed and executed asynchronously. To get the result of the
	  * job, or just know whether it's finished or not, pass a pointer to a
	  * JobState. The user keeps the ownership of the JobState, and it should
	  * not be deleted before the job ends.
	  */
	void pushJob(const Job & job, JobState * jobState=NULL)
	{
		JobQueue * selectedQueue = mJobQueues[0];
		SizeT minSize = mJobQueues[0]->size();

		for (SizeT i=1; i<mJobQueues.size(); i++)
		{
			if (mJobQueues[i]->size() < minSize)
			{
				minSize = mJobQueues[i]->size();
				selectedQueue = mJobQueues[i];
			}
		}

		selectedQueue->push(Pair<Job, JobState *>(job, jobState));
	}

	/**
	  * \brief Push a job into the job subsystem
	  */
	bool pushJob(const Job & job, SizeT affinity, JobState * jobState=NULL)
	{
		if (affinity >= maxAffinity())
			return false;
		
		mJobQueues[affinity]->push(Pair<Job, JobState *>(job, jobState));
		return true;
	}

	SizeT maxAffinity() const
	{
		return mConfiguration.mThreadCount;
	}

	/**
	  * \brief Start the job subsystem
	  *
	  * This call is blocking, and last until the QUIT message is pushed.
	  */
	void start()
	{
		mThreadPool.start();

		// Main thread = 0 thread
		run(0);
	}
};

#endif
